package pri.zxx.webdemo.aop.redisLock;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;

/**
 * @author wangn <15124178@qq.com> 2020/10/12
 */

@Slf4j
@Aspect
@Component
public class LockRedisAop {

	@Autowired
	StringRedisTemplate stringRedisTemplate;

	public static final String PREFIX = "REDIS_LOCK";

	@Pointcut(value = "@annotation(redisLock)")
	public void pointCut(RedisLock redisLock) {
	}


	@Around("@annotation(redisLock)")
	public Object doAround(ProceedingJoinPoint pjp, RedisLock redisLock) throws Throwable {
		boolean use = this.parseCondition(pjp, redisLock.condition());
		if (!use) {
			return pjp.proceed();
		}
		String key = this.parseKey(pjp, redisLock.value());
		try {
			log.info("try redis lock:{}, {}", key, redisLock);
			boolean success = this.tryLock(key, redisLock);
			if (!success) {
				log.info("get redis lock fail:{}", key);
				throw new RuntimeException(redisLock.message());
			}
			log.info("get redis lock:{}", key);
			return pjp.proceed();
		} finally {
			this.unlock(key);
			log.info("release redis lock:{}", key);
		}
	}

	private void unlock(String key) {
		String result = stringRedisTemplate.opsForValue().get(key);
		if (Objects.equals(result, Thread.currentThread().getName())) {
			stringRedisTemplate.delete(key);
		}
	}

	private boolean tryLock(String lockName, RedisLock redisLock) {
		Boolean result = stringRedisTemplate.opsForValue()
				.setIfAbsent(lockName, Thread.currentThread().getName(), redisLock.lockTime(), redisLock.timeUnit());
		if (Objects.equals(result, true)) {
			return true;
		}

		if (!redisLock.reentrant() || redisLock.tryTime() <= 0) {
			return false;
		}

		CountDownLatch countDownLatch = new CountDownLatch(1);
		RedisListener.addCountDownLatch(lockName, countDownLatch);
		try {
			if (countDownLatch.await(redisLock.tryTime(), redisLock.timeUnit())) {
				result = stringRedisTemplate.opsForValue()
						.setIfAbsent(lockName, Thread.currentThread().getName(), redisLock.lockTime(), redisLock.timeUnit());
				if (Objects.equals(result, true)) {
					return true;
				}
			}
		} catch (InterruptedException e) {
			log.warn("InterruptedException,{},{}", lockName, redisLock);
		}
		return false;
	}

	/**
	 * 获取缓存的key
	 * key 定义在注解上，支持SPEL表达式
	 *
	 * @return
	 */
	private String parseKey(ProceedingJoinPoint pjp, String el) {
		String lockName = "";
		Method method = ((MethodSignature) pjp.getSignature()).getMethod();
		if (StringUtils.isBlank(el)) {
			lockName = method.getDeclaringClass().getName() + "#" + method.getName();
		} else {
			//获取被拦截方法参数名列表(使用Spring支持类库)
			StandardReflectionParameterNameDiscoverer u = new StandardReflectionParameterNameDiscoverer();
			String[] paraNameArr = u.getParameterNames(method);
			if (Objects.isNull(paraNameArr)) {
				lockName = method.getDeclaringClass().getName() + "#" + method.getName();
			} else {
				//使用SPEL进行key的解析
				ExpressionParser parser = new SpelExpressionParser();
				//SPEL上下文
				StandardEvaluationContext context = new StandardEvaluationContext();
				//把方法参数放入SPEL上下文中
				for (int i = 0; i < paraNameArr.length; i++) {
					context.setVariable(paraNameArr[i], pjp.getArgs()[i]);
				}
				lockName = parser.parseExpression(el).getValue(context, String.class);
			}
		}

		return PREFIX + ":" + lockName;
	}

	/**
	 * 获取缓存的key
	 * key 定义在注解上，支持SPEL表达式
	 *
	 * @return
	 */
	private boolean parseCondition(ProceedingJoinPoint pjp, String el) {
		if (StringUtils.isBlank(el)) {
			return true;
		} else {
			Method method = ((MethodSignature) pjp.getSignature()).getMethod();
			//获取被拦截方法参数名列表(使用Spring支持类库)
			StandardReflectionParameterNameDiscoverer u = new StandardReflectionParameterNameDiscoverer();
			String[] paraNameArr = u.getParameterNames(method);
			//使用SPEL进行key的解析
			ExpressionParser parser = new SpelExpressionParser();
			//SPEL上下文
			StandardEvaluationContext context = new StandardEvaluationContext();
			if (Objects.nonNull(paraNameArr)) {

				//把方法参数放入SPEL上下文中
				for (int i = 0; i < paraNameArr.length; i++) {
					context.setVariable(paraNameArr[i], pjp.getArgs()[i]);
				}

			}
			Boolean result = parser.parseExpression(el).getValue(context, Boolean.class);
			return Objects.equals(result, true);
		}
	}

}
